﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Aleator.Core.Techniques;

namespace Aleator.Core
{
    /// <summary>
    /// Abstraction of phrase class
    /// </summary>
    abstract public class Phrase
    {
        #region Enums

        #pragma warning disable 1591

        /// <summary>
        /// Indicates the kind of rhythmic pattern being played. Loose is a non-repeating line across the phrase,
        /// Tight pattern repeats with each measure and rigid is every quantized note. We may need to push this functionality
        /// down into the derived DrumPhrase class.
        /// </summary>
        public enum Distribution
        {
            Loose,
            Tight,
            Rigid
        }

        #pragma warning restore 1591

        #endregion

        #region Properties

        /// <summary>
        /// Progression property
        /// </summary>
        abstract public ConcreteProgression Progression { get; set; }

        /// <summary>
        /// Notes in this phrase
        /// </summary>
        abstract public List<ConcreteNote> Notes { get; set; }

        /// <summary>
        /// Indicator of how notes are distributed across this phrase (loose, tight or rigid)
        /// </summary>
        abstract public Distribution NoteDistribution { get; set; }

        /// <summary>
        /// Number of possible note plays per quarter note.
        /// </summary>
        abstract public int Resolution { get; set;}

        /// <summary>
        /// Number of possible note plays in a measure
        /// </summary>
        abstract public int PlayableNotesInMeasure { get; }

        /// <summary>
        /// Integer representing the number of times this phrase will repeat
        /// </summary>
        abstract public int Multiplier { get; }

        /// <summary>
        /// Place holder for chord interval for pitch selction during note additions
        /// </summary>
        abstract public string ChordInterval { get; set; }

        /// <summary>
        /// Maximum number of notes that can possibly be played in this phrase
        /// </summary>
        public static int MaxNotes { get; protected set; }

        /// <summary>
        /// Number of major ticks in a measure for this phrase
        /// </summary>
        protected int MajorTicksPerMeasure
        {
            get
            {
                return Progression.Numerator * Session.SessionResolution;
            }
        }

        /// <summary>
        /// The technique type used to generate this phrase
        /// </summary>
        abstract public Technique.TechniqueType TechniqueType { get; set; }

        #endregion

        #region Protected Methods

        /// <summary>
        /// Set distribution object parameters
        /// </summary>
        /// <param name="sigma">Standard deviation</param>
        protected abstract void SetTotals(double sigma = 2.0);

        /// <summary>
        /// Get note list totals
        /// </summary>
        /// <param name="mu">Mean for normal distribution</param>
        /// <param name="sigma">Standard deviation for normal distribution</param>
        /// <returns></returns>
        protected abstract int GetTotal(double mu, double sigma);

        /// <summary>
        /// Determine the location of a note
        /// </summary>
        protected abstract void GetBeat();

        /// <summary>
        /// Transfer another phrase's notes to this one and truncate/fill out as needed
        /// </summary>
        /// <param name="p">The phrase we are copying from</param>
        protected abstract void CopyToPhrase(ConcretePhrase p);

        /// <summary>
        /// Generate not implemented in base class yet
        /// </summary>
        /// <param name="resolution"></param>
        protected abstract void Generate(int resolution);

        /// <summary>
        /// Mute all notes in the phrase
        /// </summary>
        public abstract void Mute();

        /// <summary>
        /// Unmute all notes in the phrase
        /// </summary>
        public abstract void UnMute();

        /// <summary>
        /// Determines the duration of all notes in the phrase
        /// </summary>
        protected virtual void AddRests()
        {
            //Sort note list (sort is done by beat value)
            Notes.Sort();

            //Go through the sorted note list and calculate the gaps between all notes
            for (int i = 0; i < Notes.Count; i++)
            {
                ConcreteNote n = Notes[i];

                if (i == Notes.Count - 1)
                    n.Gap = PlayableNotesInMeasure - n.Location + Notes[0].Location;
                else if (Notes[i + 1].Measure != n.Measure)
                    n.Gap = PlayableNotesInMeasure - n.Location + Notes[i + 1].Location;
                else
                    n.Gap = Notes[i + 1].Location - n.Location;

                if (n.Gap < 0)
                    throw new Exception(string.Format("Gap is {0} for note at measure {1}, location {2}", n.Gap, n.Measure, n.Location));
            }

            //Group the note list by gap value
            var result = Notes.GroupBy(a => a.Gap);
            int off;

            //For each group, randomly pick a number less than the gap value and play the notes for that duration
            foreach (var group in result)
            {
                //If gap is one, we can't offset the off note any more than that
                if (group.Key == 1)
                    off = 1;
                else
                    off = Session.RndGenerator.Next(1, group.Key - 1);

                try
                {
                    //Go through all of the notes with this gap length and set the off note values
                    foreach (ConcreteNote n in group)
                    {
                        n.OffLocation = n.Location + off;

                        //If the offbeat to the appropriate beat in the next measure. Otherwise the note is turned off in the same measure.
                        if (n.OffLocation >= PlayableNotesInMeasure - 1)
                        {
                            if (n.Measure == Progression.TotalMeasures - 1)
                            {
                                n.OffLocation = PlayableNotesInMeasure - 1;
                                n.OffMeasure = n.Measure;
                            }
                            else
                            {
                                n.OffLocation -= PlayableNotesInMeasure - 1;
                                n.OffMeasure = n.Measure + 1;
                            }
                        }
                        else
                            n.OffMeasure = n.Measure;
                    }
                }
                catch (Exception)
                {
                    //Swallow exceptions here for now
                }
            }
        }

        /// <summary>
        /// Tight not implemented in base class yet
        /// </summary>
        protected virtual void Tight()
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Loose note implemented in base class yet
        /// </summary>
        protected virtual void Loose()
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Rigid not implemented in base class yet
        /// </summary>
        protected virtual void Rigid()
        {
            throw new NotImplementedException();
        }

        #endregion
    }
}
